/*
 * Copyright 2021 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.yb.xczx.cloud.auth.config;

import java.lang.reflect.Type;
import java.security.KeyPair;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.time.Duration;
import java.time.Instant;
import java.util.*;
import java.util.stream.Collectors;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.Module;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.RSAKey;
import com.nimbusds.jose.jwk.source.ImmutableJWKSet;
import com.nimbusds.jose.jwk.source.JWKSource;
import com.nimbusds.jose.proc.SecurityContext;

import com.yb.xczx.cloud.auth.bean.XczxSysUser;
import com.yb.xczx.cloud.auth.bean.XczxUserPassAuthenticationToken;
import com.yb.xczx.cloud.auth.deserializer.module.XczxOauth2Jackson2Module;
import com.yb.xczx.cloud.auth.entity.SysUser;
import com.yb.xczx.cloud.auth.filter.XczxUserAuthenticationFilter;
import com.yb.xczx.cloud.auth.handler.XczxOauth2AuthenticationEntryPoint;
import com.yb.xczx.cloud.auth.oauth2.repository.XczxRegisteredClientRepository;
import com.yb.xczx.cloud.auth.oauth2.service.JdbcOAuth2AuthorizationService;
import com.yb.xczx.cloud.auth.oauth2.service.XczxOAuth2AuthorizationService;
import lombok.extern.slf4j.Slf4j;
import org.checkerframework.checker.units.qual.A;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.context.annotation.Role;
import org.springframework.core.annotation.Order;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.jackson2.SecurityJackson2Modules;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
import org.springframework.security.oauth2.core.oidc.OidcScopes;
import org.springframework.security.oauth2.jwt.*;
import org.springframework.security.oauth2.server.authorization.InMemoryOAuth2AuthorizationConsentService;
import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationConsentService;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeRequestAuthenticationProvider;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationConsentAuthenticationProvider;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationEndpointConfigurer;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer;
import org.springframework.security.oauth2.server.authorization.jackson2.OAuth2AuthorizationServerJackson2Module;
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
import org.springframework.security.oauth2.server.authorization.settings.ClientSettings;
import org.springframework.security.oauth2.server.authorization.settings.OAuth2TokenFormat;
import org.springframework.security.oauth2.server.authorization.settings.TokenSettings;
import org.springframework.security.oauth2.server.authorization.token.*;
import org.springframework.security.oauth2.server.authorization.web.XczxOAuth2AuthorizationEndpointFilter;
import org.springframework.security.web.DefaultSecurityFilterChain;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
import org.springframework.security.web.authentication.logout.LogoutFilter;

import javax.sql.DataSource;

/**
 * OAuth Authorization Server Configuration.
 *
 * @author Steve Riesenberg
 */
@Configuration
@Slf4j
public class OAuth2AuthorizationServerSecurityConfig implements InitializingBean {


	@Autowired
	XczxUserAuthenticationFilter xczxUserAuthenticationFilter;
	@Autowired
	PasswordEncoder passwordEncoder;

	@Value("${jwt.public.key}")
	RSAPublicKey key;

	@Value("${jwt.private.key}")
	RSAPrivateKey priv;

	@Autowired
	JwtDecoder jwtDecoder;

	@Bean
	public AuthenticationManager oauth2AuthenticationManager(AuthenticationConfiguration authenticationConfiguration,JdbcTemplate jdbcTemplate) throws Exception {
		ProviderManager manager = (ProviderManager) authenticationConfiguration.getAuthenticationManager();
		manager.getProviders().add(new OAuth2AuthorizationCodeRequestAuthenticationProvider(registeredClientRepository(),authorizationService(jdbcTemplate,registeredClientRepository()),oAuth2AuthorizationConsentService(jdbcTemplate,registeredClientRepository())));
		manager.getProviders().add(new OAuth2AuthorizationConsentAuthenticationProvider(registeredClientRepository(),authorizationService(jdbcTemplate,registeredClientRepository()),oAuth2AuthorizationConsentService(jdbcTemplate,registeredClientRepository())));
		return manager;
	}


	@Autowired
	@Qualifier("oauth2AuthenticationManager")
	@Lazy
	AuthenticationManager oauth2AuthenticationManager;

	@Bean
	@Order(1)
	public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {


		OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);

		http.getConfigurer(OAuth2AuthorizationServerConfigurer.class).oidc(Customizer.withDefaults());

		http
				// Redirect to the login page when not authenticated from the
				// authorization endpoint
				.exceptionHandling((exceptions) -> exceptions
						.authenticationEntryPoint(
								new XczxOauth2AuthenticationEntryPoint())
				);

		http.getConfigurer(OAuth2AuthorizationServerConfigurer.class).authorizationEndpoint(authorizationEndpoint ->
				authorizationEndpoint.consentPage("/oauth2/consent/custom")
		);

		http.addFilterAfter(xczxUserAuthenticationFilter, LogoutFilter.class);
//		AuthenticationManager authenticationManager = http.getSharedObject(AuthenticationManager.class);
//		http.addFilterAfter(new XczxOAuth2AuthorizationEndpointFilter(authenticationManager),XczxUserAuthenticationFilter.class);
		http.addFilterAfter(new XczxOAuth2AuthorizationEndpointFilter(oauth2AuthenticationManager),XczxUserAuthenticationFilter.class);
		DefaultSecurityFilterChain build = http.build();
		System.out.println("filters:{}"+build.getFilters());

		return build;
	}




	@Bean
	public RegisteredClientRepository registeredClientRepository() {
		// @formatter:off
		RegisteredClient loginClient = RegisteredClient.withId("13abfa80-5420-4f68-a549-77b257e31055")
				.clientId("login-client")
				.clientSecret(passwordEncoder.encode("openid-connect"))
				.clientIdIssuedAt(Instant.now())
				.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
				.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
				.authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
				.redirectUri("http://passport.51xuecheng.cn:8080/login/oauth2/code/login-client")
				.redirectUri("http://passport.51xuecheng.cn:8080/authorized")
				.scope(OidcScopes.OPENID)
				.scope(OidcScopes.PROFILE)
				.scope(OidcScopes.ADDRESS)
				.scope(OidcScopes.EMAIL)
				.scope(OidcScopes.PHONE)
				.scope("custom")
				.clientSettings(ClientSettings.builder().requireAuthorizationConsent(false).build())
				.tokenSettings(TokenSettings.builder().accessTokenTimeToLive(Duration.ofDays(7l)).refreshTokenTimeToLive(Duration.ofDays(7l))
						.build())
				.build();

		RegisteredClient helloClient = RegisteredClient.withId("13abfa80-5420-4f68-a549-77b257e31056")
				.clientId("hello")
				.clientSecret(passwordEncoder.encode("hello"))
				.clientIdIssuedAt(Instant.now())
				.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
				.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
				.authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
				.redirectUri("http://gateway.51xuecheng.cn:63040/inner/passport/login/oauth2/code/hello")
				.redirectUri("http://gateway.51xuecheng.cn:63040/inner/passport/authorized")
				.scope(OidcScopes.OPENID)
				.scope(OidcScopes.PROFILE)
				.scope(OidcScopes.ADDRESS)
				.scope(OidcScopes.EMAIL)
				.scope(OidcScopes.PHONE)
				.scope("custom")
				.clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build())
				.tokenSettings(TokenSettings.builder()
								.accessTokenFormat(OAuth2TokenFormat.REFERENCE)
						.accessTokenTimeToLive(Duration.ofDays(7l)).refreshTokenTimeToLive(Duration.ofDays(7l))
						.build())
				.build();

		RegisteredClient registeredClient = RegisteredClient.withId("4ebf9f12-32be-4ccc-884b-5df9ec2e4fb8")
				.clientId("messaging-client")
				.clientSecret(passwordEncoder.encode("secret"))
				.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
				.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
				.scope("message:read")
				.scope("message:write")
				.build();

		RegisteredClient opaqueClient = RegisteredClient.withId("4ebf9f12-32be-4ccc-884b-5df9ec2e4fb9")
				.clientId("opaque")
				.clientSecret(passwordEncoder.encode("opaque"))
				.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
				.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
				.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
				.redirectUri("http://passport.51xuecheng.cn:8080/login/oauth2/code/opaque")
				.redirectUri("http://passport.51xuecheng.cn:8080/authorized")
				.build();
		// @formatter:on

		return new XczxRegisteredClientRepository(loginClient,helloClient, opaqueClient,registeredClient);
	}

	@Bean
	public JWKSource<SecurityContext> jwkSource(KeyPair keyPair) {
		RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
		RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
		// @formatter:off
		RSAKey rsaKey = new RSAKey.Builder(publicKey)
				.privateKey(privateKey)
				.keyID(UUID.randomUUID().toString())
				.build();
		// @formatter:on
		JWKSet jwkSet = new JWKSet(rsaKey);
		return new ImmutableJWKSet<>(jwkSet);
	}


	@Bean
	public AuthorizationServerSettings providerSettings() {
		return AuthorizationServerSettings.builder().issuer("http://localhost:9000").build();
	}

	@Bean
	OAuth2AuthorizationConsentService oAuth2AuthorizationConsentService(JdbcTemplate jdbcTemplate,
																		RegisteredClientRepository registeredClientRepository){
		return new JdbcOAuth2AuthorizationConsentService(jdbcTemplate,registeredClientRepository);
	}


//	@Bean
//	OAuth2AuthorizationService auth2AuthorizationService(){
//		return new JdbcOAuth2AuthorizationService(jdbcTemplate,registeredClientRepository());
//	}


	@Bean
	public OAuth2AuthorizationService authorizationService(JdbcTemplate jdbcTemplate,
														   RegisteredClientRepository registeredClientRepository) {
		XczxOAuth2AuthorizationService authorizationService =
				new XczxOAuth2AuthorizationService(jdbcTemplate, registeredClientRepository);
		XczxOAuth2AuthorizationService.OAuth2AuthorizationRowMapper rowMapper = new XczxOAuth2AuthorizationService.OAuth2AuthorizationRowMapper(registeredClientRepository);
		ClassLoader classLoader = JdbcOAuth2AuthorizationService.class.getClassLoader();
		List<Module> securityModules = SecurityJackson2Modules.getModules(classLoader);
		ObjectMapper objectMapper = new ObjectMapper();
		objectMapper.registerModules(securityModules);
		objectMapper.registerModule(new OAuth2AuthorizationServerJackson2Module());
		objectMapper.registerModule(new XczxOauth2Jackson2Module());
		rowMapper.setObjectMapper(objectMapper);
		authorizationService.setAuthorizationRowMapper(rowMapper);
		return authorizationService;
	}

	@Bean
	public OAuth2TokenCustomizer<JwtEncodingContext> accessTokenCustomizer(UserDetailsService userDetailsService,OAuth2AuthorizationConsentService oAuth2AuthorizationConsentService) {
		return context -> {
			JwtClaimsSet.Builder claims = context.getClaims();
			User user = (User) context.getPrincipal().getPrincipal();
			Set<String> authorizedScopes = context.getAuthorizedScopes();
			Collection<? extends GrantedAuthority> authorities = userDetailsService.loadUserByUsername(user.getUsername()).getAuthorities();
			List<String> list = authorities.stream().map(item -> item.getAuthority()).collect(Collectors.toList());
			authorizedScopes.stream().forEach(item->{list.add("SCOPE_"+item);});
			log.info("scopeList:{}",list);
			claims.claim("scope",list);

			// Customize claims
		};
	}

	@Bean
	public OAuth2TokenCustomizer<OAuth2TokenClaimsContext> tokenClaimsContextOAuth2TokenCustomizer(UserDetailsService userDetailsService, OAuth2AuthorizationConsentService oAuth2AuthorizationConsentService) {
		return context -> {
			DefaultOAuth2TokenContext defaultOAuth2TokenContext;
			OAuth2TokenClaimsSet.Builder claims = context.getClaims();
			User user = (User) context.getPrincipal().getPrincipal();
			Set<String> authorizedScopes = context.getAuthorizedScopes();
			Collection<? extends GrantedAuthority> authorities = userDetailsService.loadUserByUsername(user.getUsername()).getAuthorities();
			List<String> list = authorities.stream().map(item -> item.getAuthority()).collect(Collectors.toList());
			authorizedScopes.stream().forEach(item->{list.add("SCOPE_"+item);});
			log.info("scopeList:{}",list);
			claims.claim("scope",list);

			// Customize claims
		};
	}


//	@Bean
//	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
//	KeyPair generateRsaKey() {
//		KeyPair keyPair;
//		try {
//			KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
//			keyPairGenerator.initialize(2048);
//			keyPair = keyPairGenerator.generateKeyPair();
//		}
//		catch (Exception ex) {
//			throw new IllegalStateException(ex);
//		}
//		return keyPair;
//	}
	@Bean
	JwtDecoder jwtDecoder() {
	return NimbusJwtDecoder.withPublicKey(this.key).build();
}

//	@Bean
//	JwtEncoder jwtEncoder() {
//		JWK jwk = new RSAKey.Builder(this.key).privateKey(this.priv).build();
//		JWKSource<SecurityContext> jwks = new ImmutableJWKSet<>(new JWKSet(jwk));
//		return new NimbusJwtEncoder(jwks);
//	}
	@Bean
	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
	KeyPair keyPair(){
		return new KeyPair(this.key,this.priv);
	}



	@Override
	public void afterPropertiesSet() throws Exception {
	}

	public static void main(String[] args) throws JsonProcessingException {
		String result="{\"@class\":\"java.util.Collections$UnmodifiableMap\",\"java.security.Principal\":{\"@class\":\"com.yb.xczx.cloud.auth.bean.XczxUserPassAuthenticationToken\",\"authorities\":[\"java.util.Collections$UnmodifiableRandomAccessList\",[{\"@class\":\"org.springframework.security.core.authority.SimpleGrantedAuthority\",\"authority\":\"SUPER_ADMIN\"}]],\"details\":null,\"authenticated\":true,\"xczxSysUser\":{\"@class\":\"com.yb.xczx.cloud.auth.bean.XczxSysUser\",\"password\":\"$2a$10$S5yaxM4OqGbfeXqag/k.NOZZ.5sygpz/XDULfsMkB5U5dO.ZYmMA6\",\"username\":\"admin\",\"authorities\":null,\"accountNonExpired\":true,\"accountNonLocked\":true,\"credentialsNonExpired\":true,\"enabled\":true,\"roles\":null,\"permissions\":null,\"sysUser\":{\"@class\":\"com.yb.xczx.cloud.auth.entity.SysUser\",\"id\":1,\"userId\":0,\"username\":\"admin\",\"password\":\"$2a$10$S5yaxM4OqGbfeXqag/k.NOZZ.5sygpz/XDULfsMkB5U5dO.ZYmMA6\",\"encryptedType\":\"1\",\"enabled\":1,\"accountNonExpired\":1,\"accountNonLocked\":1,\"credentialsNonExpired\":1,\"createTime\":[2023,5,17,8,50,39],\"updateTime\":[2023,5,17,8,52,2]},\"loginAccessToken\":null,\"oauth2AccessToken\":null},\"loginType\":\"USER_PASS\",\"loginAccessToken\":null,\"oauth2AccessToken\":null,\"jwtToken\":null,\"name\":\"admin\",\"username\":\"admin\",\"credentials\":\"$2a$10$S5yaxM4OqGbfeXqag/k.NOZZ.5sygpz/XDULfsMkB5U5dO.ZYmMA6\",\"userId\":0,\"principal\":{\"@class\":\"com.yb.xczx.cloud.auth.bean.XczxSysUser\",\"password\":\"$2a$10$S5yaxM4OqGbfeXqag/k.NOZZ.5sygpz/XDULfsMkB5U5dO.ZYmMA6\",\"username\":\"admin\",\"authorities\":null,\"accountNonExpired\":true,\"accountNonLocked\":true,\"credentialsNonExpired\":true,\"enabled\":true,\"roles\":null,\"permissions\":null,\"sysUser\":{\"@class\":\"com.yb.xczx.cloud.auth.entity.SysUser\",\"id\":1,\"userId\":0,\"username\":\"admin\",\"password\":\"$2a$10$S5yaxM4OqGbfeXqag/k.NOZZ.5sygpz/XDULfsMkB5U5dO.ZYmMA6\",\"encryptedType\":\"1\",\"enabled\":1,\"accountNonExpired\":1,\"accountNonLocked\":1,\"credentialsNonExpired\":1,\"createTime\":[2023,5,17,8,50,39],\"updateTime\":[2023,5,17,8,52,2]},\"loginAccessToken\":null,\"oauth2AccessToken\":null}},\"org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest\":{\"@class\":\"org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest\",\"authorizationUri\":\"http://localhost:9000/oauth2/authorize\",\"authorizationGrantType\":{\"value\":\"authorization_code\"},\"responseType\":{\"value\":\"code\"},\"clientId\":\"login-client\",\"redirectUri\":\"http://client:8080/login/oauth2/code/login-client\",\"scopes\":[\"java.util.Collections$UnmodifiableSet\",[\"openid\",\"profile\"]],\"state\":null,\"additionalParameters\":{\"@class\":\"java.util.Collections$UnmodifiableMap\",\"access_token\":\"79daad3a47475954a03ef1746035a6f0\"},\"authorizationRequestUri\":\"http://localhost:9000/oauth2/authorize?response_type=code&client_id=login-client&scope=openid%20profile&redirect_uri=http://client:8080/login/oauth2/code/login-client&access_token=79daad3a47475954a03ef1746035a6f0\",\"attributes\":{\"@class\":\"java.util.Collections$UnmodifiableMap\"}}}";
		ObjectMapper objectMapper = new ObjectMapper();
		ClassLoader classLoader = JdbcOAuth2AuthorizationService.class.getClassLoader();
		List<Module> securityModules = SecurityJackson2Modules.getModules(classLoader);
		objectMapper.registerModules(securityModules);
		objectMapper.registerModule(new OAuth2AuthorizationServerJackson2Module());
		objectMapper.registerModule(new XczxOauth2Jackson2Module());
		Map map = objectMapper.readValue(result, new TypeReference<Map<String,Object>>() {
		});
		System.out.println(map);
		System.out.println(map.get(OAuth2AuthorizationRequest.class.getName()).getClass());

//
//		Collection<GrantedAuthority> authorities=new HashSet<>();
//		authorities.add(new SimpleGrantedAuthority("USER"));
//		authorities.add(new SimpleGrantedAuthority("USER"));
//
//		System.out.println(objectMapper.writeValueAsString(authorities));
//
//		authorities.add(new SimpleGrantedAuthority("USER"));
//
//		SysUser sysUser = new SysUser();
//		sysUser.setUsername("admin");
//		sysUser.setPassword("admin");
//		sysUser.setAccountNonExpired(1);
//		sysUser.setAccountNonLocked(1);
//		sysUser.setCredentialsNonExpired(1);
//		sysUser.setEnabled(1);
//		XczxSysUser xczxSysUser = new XczxSysUser(sysUser,authorities);
//		XczxUserPassAuthenticationToken authenticated = XczxUserPassAuthenticationToken.authenticated(xczxSysUser, xczxSysUser.getAuthorities());
//		System.out.println(objectMapper.writeValueAsString(authenticated));


	}
}
